#
# This file is part of LiteX.
#
# This file is Copyright (c) 2013-2014 Sebastien Bourdeauducq <sb@m-labs.hk>
# This file is Copyright (c) 2014-2019 Florent Kermarrec <florent@enjoy-digital.fr>
# This file is Copyright (c) 2018 Dolu1990 <charles.papon.90@gmail.com>
# This file is Copyright (c) 2019 Gabriel L. Somlo <gsomlo@gmail.com>
# This file is Copyright (c) 2019 Ilia Sergachev <ilia.sergachev@protonmail.ch>
# This file is Copyright (c) 2018 Jean-François Nguyen <jf@lambdaconcept.fr>
# This file is Copyright (c) 2020 Raptor Engineering, LLC <sales@raptorengineering.com>
# This file is Copyright (c) 2015 Robert Jordens <jordens@gmail.com>
# This file is Copyright (c) 2018 Sean Cross <sean@xobs.io>
# This file is Copyright (c) 2018 Stafford Horne <shorne@gmail.com>
# This file is Copyright (c) 2018-2017 Tim 'mithro' Ansell <me@mith.ro>
# This file is Copyright (c) 2015 whitequark <whitequark@whitequark.org>
# This file is Copyright (c) 2014 Yann Sionneau <ys@m-labs.hk>
# SPDX-License-Identifier: BSD-2-Clause

import os
import inspect

from migen import *

from litex.soc.cores import cpu
from litex.soc.interconnect import wishbone
from litex.soc.integration.common import *
from litex.soc.integration.soc import *

__all__ = [
    "mem_decoder",
    "get_mem_data",
    "SoCCore",
    "soc_core_args",
    "soc_core_argdict",
    "SoCMini",
    "soc_mini_args",
    "soc_mini_argdict",
]

# Helpers ------------------------------------------------------------------------------------------

def mem_decoder(address, size=0x10000000):
    size = 2**log2_int(size, False)
    assert (address & (size - 1)) == 0
    address >>= 2 # bytes to words aligned
    size    >>= 2 # bytes to words aligned
    return lambda a: (a[log2_int(size):] == (address >> log2_int(size)))

# SoCCore ------------------------------------------------------------------------------------------

class SoCCore(LiteXSoC):
    # Default register/interrupt/memory mappings (can be redefined by user)
    csr_map       = {}
    interrupt_map = {}
    mem_map       = {
        "rom":      0x00000000,
        "sram":     0x01000000,
        "main_ram": 0x40000000,
    }

    def __init__(self, platform, clk_freq,
        # Bus parameters
        bus_standard             = "wishbone",
        bus_data_width           = 32,
        bus_address_width        = 32,
        bus_timeout              = 1e6,

        # CPU parameters
        cpu_type                 = "vexriscv",
        cpu_reset_address        = None,
        cpu_variant              = None,
        cpu_cls                  = None,
        cpu_cfu                  = None,

        # CFU parameters
        cfu_filename             = None,

        # ROM parameters
        integrated_rom_size      = 0,
        integrated_rom_mode      = "r",
        integrated_rom_init      = [],
        integrated_rom_no_we     = False,

        # SRAM parameters
        integrated_sram_size     = 0x2000,
        integrated_sram_init     = [],
        integrated_sram_no_we    = False,

        # MAIN_RAM parameters
        integrated_main_ram_size  = 0,
        integrated_main_ram_init  = [],
        integrated_main_ram_no_we = False,

        # CSR parameters
        csr_data_width           = 32,
        csr_address_width        = 14,
        csr_paging               = 0x800,
        csr_ordering             = "big",

        # Interrupt parameters
        irq_n_irqs               = 32,

        # Identifier parameters
        ident                    = "",
        ident_version            = False,

        # UART parameters
        with_uart                = True,
        uart_name                = "serial",
        uart_baudrate            = 115200,
        uart_fifo_depth          = 16,

        # Timer parameters
        with_timer               = True,
        timer_uptime             = False,

        # Controller parameters
        with_ctrl                = True,

        # Others
        **kwargs):

        # New LiteXSoC class ----------------------------------------------------------------------------
        LiteXSoC.__init__(self, platform, clk_freq,
            bus_standard         = bus_standard,
            bus_data_width       = bus_data_width,
            bus_address_width    = bus_address_width,
            bus_timeout          = bus_timeout,
            bus_reserved_regions = {},

            csr_data_width       = csr_data_width,
            csr_address_width    = csr_address_width,
            csr_paging           = csr_paging,
            csr_ordering         = csr_ordering,
            csr_reserved_csrs    = self.csr_map,

            irq_n_irqs           = irq_n_irqs,
            irq_reserved_irqs    = {},
        )

        # Attributes
        self.mem_regions = self.bus.regions
        self.clk_freq    = self.sys_clk_freq
        self.mem_map     = self.mem_map
        self.config      = {}

        # Parameters management --------------------------------------------------------------------

        # CPU.
        cpu_type          = None if cpu_type == "None" else cpu_type
        cpu_reset_address = None if cpu_reset_address == "None" else cpu_reset_address

        self.cpu_type     = cpu_type
        self.cpu_variant  = cpu_variant
        self.cpu_cls      = cpu_cls

        # ROM.
        # Initialize ROM from binary file when provided.
        if isinstance(integrated_rom_init, str):
            integrated_rom_init = get_mem_data(integrated_rom_init, "little") # FIXME: Endianness.
            integrated_rom_size = 4*len(integrated_rom_init)

        # Disable ROM when no CPU/hard-CPU.
        if cpu_type in [None, "zynq7000"]:
            integrated_rom_init = []
            integrated_rom_size = 0
        self.integrated_rom_size        = integrated_rom_size
        self.integrated_rom_initialized = integrated_rom_init != []

        # SRAM.
        self.integrated_sram_size = integrated_sram_size

        # MAIN RAM.
        self.integrated_main_ram_size = integrated_main_ram_size

        # CSRs.
        self.csr_data_width = csr_data_width

        # Wishbone Slaves.
        self.wb_slaves = {}

        # Modules instances ------------------------------------------------------------------------

        # Add SoCController
        if with_ctrl:
            self.add_controller("ctrl")

        # Add CPU
        self.add_cpu(
            name          = str(cpu_type),
            variant       = "standard" if cpu_variant is None else cpu_variant,
            reset_address = None if integrated_rom_size else cpu_reset_address,
            cls           = cpu_cls,
            cfu           = cpu_cfu)

        # Add User's interrupts
        if self.irq.enabled:
            for name, loc in self.interrupt_map.items():
                self.irq.add(name, loc)

        # Add integrated ROM
        if integrated_rom_size:
            self.add_rom("rom",
                origin   = self.cpu.reset_address,
                size     = integrated_rom_size,
                contents = integrated_rom_init,
                mode     = integrated_rom_mode,
                no_we    = integrated_rom_no_we
            )

        # Add integrated SRAM
        if integrated_sram_size:
            self.add_ram("sram",
                origin = self.mem_map["sram"],
                size   = integrated_sram_size,
                no_we  = integrated_sram_no_we
            )

        # Add integrated MAIN_RAM (only useful when no external SRAM/SDRAM is available)
        if integrated_main_ram_size:
            self.add_ram("main_ram",
                origin   = self.mem_map["main_ram"],
                size     = integrated_main_ram_size,
                contents = integrated_main_ram_init,
                no_we    = integrated_main_ram_no_we
            )

        # Add Identifier
        if ident != "":
            self.add_identifier("identifier", identifier=ident, with_build_time=ident_version)

        # Add UART
        if with_uart:
            self.add_uart(name=uart_name, baudrate=uart_baudrate, fifo_depth=uart_fifo_depth)

        # Add Timer
        if with_timer:
            self.add_timer(name="timer0")
            if timer_uptime:
                self.timer0.add_uptime()

    # Methods --------------------------------------------------------------------------------------

    def add_interrupt(self, interrupt_name, interrupt_id=None, use_loc_if_exists=False):
        self.irq.add(interrupt_name, interrupt_id, use_loc_if_exists=use_loc_if_exists)

    def add_csr(self, csr_name, csr_id=None, use_loc_if_exists=False):
        self.csr.add(csr_name, csr_id, use_loc_if_exists=use_loc_if_exists)

    def initialize_rom(self, data):
        self.init_rom(name="rom", contents=data)

    def add_wb_master(self, wbm):
        self.bus.add_master(master=wbm)

    def add_wb_slave(self, address, interface, size=None):
        wb_name = None
        for name, region in self.bus.regions.items():
            if address == region.origin:
                wb_name = name
                break
        if wb_name is None:
            self.wb_slaves[address] = interface
        else:
            self.bus.add_slave(name=wb_name, slave=interface)

    def add_memory_region(self, name, origin, length, type="cached"):
        self.bus.add_region(name, SoCRegion(origin=origin, size=length,
            cached="cached" in type,
            linker="linker" in type))

    def register_mem(self, name, address, interface, size=0x10000000):
        self.bus.add_slave(name, interface, SoCRegion(origin=address, size=size))

    def register_rom(self, interface, rom_size=0xa000):
        self.bus.add_slave("rom", interface, SoCRegion(origin=self.cpu.reset_address, size=rom_size))

    def add_csr_region(self, name, origin, busword, obj):
        self.csr_regions[name] = SoCCSRRegion(origin, busword, obj)

    # Finalization ---------------------------------------------------------------------------------

    def do_finalize(self):
        # Retro-compatibility
        for address, interface in self.wb_slaves.items():
            wb_name = None
            for name, region in self.bus.regions.items():
                if address == region.origin:
                    wb_name = name
                    break
            self.bus.add_slave(name=wb_name, slave=interface)

        SoC.do_finalize(self)
        # Retro-compatibility
        for region in self.bus.regions.values():
            region.length = region.size
            region.type   = "cached" if region.cached else "io"
            if region.linker:
                region.type += "+linker"
        self.csr_regions = self.csr.regions
        for name, value in self.config.items():
            self.add_config(name, value)

# SoCCore arguments --------------------------------------------------------------------------------

def soc_core_args(parser):
    # Bus parameters
    parser.add_argument("--bus-standard",      default="wishbone",                help="Select bus standard: {}, (default=wishbone).".format(", ".join(SoCBusHandler.supported_standard)))
    parser.add_argument("--bus-data-width",    default=32,         type=auto_int, help="Bus data-width (default=32).")
    parser.add_argument("--bus-address-width", default=32,         type=auto_int, help="Bus address-width (default=32).")
    parser.add_argument("--bus-timeout",       default=1e6,        type=float,    help="Bus timeout in cycles (default=1e6).")

    # CPU parameters
    parser.add_argument("--cpu-type",          default=None,                     help="Select CPU: {}, (default=vexriscv).".format(", ".join(iter(cpu.CPUS.keys()))))
    parser.add_argument("--cpu-variant",       default=None,                     help="CPU variant (default=standard).")
    parser.add_argument("--cpu-reset-address", default=None,      type=auto_int, help="CPU reset address (default=None : Boot from Integrated ROM).")
    parser.add_argument("--cpu-cfu",           default=None,                     help="Optional CPU CFU file/instance to add to the CPU.")

    # Controller parameters
    parser.add_argument("--no-ctrl", action="store_true", help="Disable Controller (default=False).")

    # ROM parameters
    parser.add_argument("--integrated-rom-size", default=0x20000, type=auto_int, help="Size/Enable the integrated (BIOS) ROM (default=128KB, automatically resized to BIOS size when smaller).")
    parser.add_argument("--integrated-rom-init", default=None,    type=str,      help="Integrated ROM binary initialization file (override the BIOS when specified).")

    # SRAM parameters
    parser.add_argument("--integrated-sram-size", default=0x2000, type=auto_int, help="Size/Enable the integrated SRAM (default=8KB).")

    # MAIN_RAM parameters
    parser.add_argument("--integrated-main-ram-size", default=None, type=auto_int, help="size/enable the integrated main RAM")

    # CSR parameters
    parser.add_argument("--csr-data-width",    default=None,  type=auto_int, help="CSR bus data-width (8 or 32, default=32).")
    parser.add_argument("--csr-address-width", default=14,    type=auto_int, help="CSR bus address-width.")
    parser.add_argument("--csr-paging",        default=0x800, type=auto_int, help="CSR bus paging.")
    parser.add_argument("--csr-ordering",      default="big",                help="CSR registers ordering (default=big).")

    # Identifier parameters
    parser.add_argument("--ident",             default=None,  type=str,  help="SoC identifier (default=\"\").")
    parser.add_argument("--ident-version",     default=None,  type=bool, help="Add date/time to SoC identifier (default=False)")

    # UART parameters
    parser.add_argument("--no-uart",         action="store_true",                help="Disable UART (default=False).")
    parser.add_argument("--uart-name",       default="serial",    type=str,      help="UART type/name (default=serial).")
    parser.add_argument("--uart-baudrate",   default=None,        type=auto_int, help="UART baudrate (default=115200).")
    parser.add_argument("--uart-fifo-depth", default=16,          type=auto_int, help="UART FIFO depth (default=16).")

    # Timer parameters
    parser.add_argument("--no-timer",        action="store_true", help="Disable Timer (default=False).")
    parser.add_argument("--timer-uptime",    action="store_true", help="Add an uptime capability to Timer (default=False).")

    # L2 Cache
    parser.add_argument("--l2-size",           default=8192, type=auto_int, help="L2 cache size (default=8192).")

def soc_core_argdict(args):
    r = dict()
    for a in inspect.getfullargspec(SoCCore.__init__).args:
        if a not in ["self", "platform"]:
            if a in ["with_uart", "with_timer", "with_ctrl"]:
                arg = not getattr(args, a.replace("with", "no"), True)
            else:
                arg = getattr(args, a, None)
            if arg is not None:
                r[a] = arg
    return r

# SoCMini ---------------------------------------------------------------------------------------

class SoCMini(SoCCore):
     def __init__(self, *args, **kwargs):
        if "cpu_type" not in kwargs.keys():
            kwargs["cpu_type"] = "None"
        if "integrated_sram_size" not in kwargs.keys():
            kwargs["integrated_sram_size"] = 0
        if "with_uart" not in kwargs.keys():
            kwargs["with_uart"] = False
        if "with_timer" not in kwargs.keys():
            kwargs["with_timer"] = False

        SoCCore.__init__(self, *args, **kwargs)

# SoCMini arguments -----------------------------------------------------------------------------

soc_mini_args    = soc_core_args
soc_mini_argdict = soc_core_argdict
